﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.PeerResolvers;
using System.Text;
using log4net;

namespace WCFPeerResolver
{
    /// <summary>
    /// A in memory storage manager database.
    /// </summary>
    public class InMemoryStorageManager : IPeerStorageManager
    {
        private readonly MeshManager m_Meshes = new MeshManager();
        private readonly object m_SyncObj = new object();

        private readonly TimeSpan m_RegistrationLifetime;
        
        private readonly ILog m_Log = LogManager.GetLogger("Storage");
        
        /// <summary>
        /// This property has to be larger than registration lifetime.
        /// </summary>
        private readonly TimeSpan m_CleanupInterval;

        /// <summary>
        /// Last time a cleanup attempt was fired.
        /// </summary>
        private DateTime m_LastCleanup;


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="cleanupInterval">Cleanup interval, has to be larger than registration lifetime.</param>
        /// <param name="registrationLifetime">Registration lifetime, has to be smaller than cleanup interval.</param>
        public InMemoryStorageManager(TimeSpan cleanupInterval, TimeSpan registrationLifetime)
        {
            m_CleanupInterval = cleanupInterval;
            m_RegistrationLifetime = registrationLifetime;
        }

        /// <summary>
        /// Clean up dead meshes and entries.
        /// Called by all methods except GetServiceSettings.
        /// </summary>
        private void Cleanup()
        {
            DateTime time = DateTime.UtcNow;

            if (time - m_LastCleanup < m_CleanupInterval)
                return;

            if (m_Log.IsDebugEnabled)
                m_Log.Debug("Running cleanup.");

            m_Meshes.Cleanup(time, m_CleanupInterval);

            m_LastCleanup = time;
        }

        /// <summary>
        /// Handle the registration call from a client and creates an mesh entry. A mesh is created if it does not exist.
        /// </summary>
        /// <param name="registerInfo"></param>
        /// <returns></returns>
        RegisterResponseInfo IPeerResolverContract.Register(RegisterInfo registerInfo)
        {
            MeshEntry entry = new MeshEntry(registerInfo.ClientId, registerInfo.NodeAddress);

            Mesh mesh;
            lock (m_SyncObj)
            {
                Cleanup();

                mesh = m_Meshes.GetOrCreate(registerInfo.MeshId);
                mesh.Add(entry);

                return new RegisterResponseInfo(entry.RegistrationID, m_RegistrationLifetime);
            }
        }

        /// <summary>
        /// Updates a mesh entry if it exists.
        /// </summary>
        /// <param name="updateInfo"></param>
        /// <returns>Returns Register response if the entry exists, null otherwise.</returns>
        RegisterResponseInfo IPeerResolverContract.Update(UpdateInfo updateInfo)
        {
            MeshEntry entry = new MeshEntry(updateInfo.ClientId, updateInfo.NodeAddress);

            Mesh mesh;
            lock (m_SyncObj)
            {
                Cleanup();

                mesh = m_Meshes.Get(updateInfo.MeshId);
                if (mesh != null)
                {
                    mesh.Update(entry);
                    return new RegisterResponseInfo(entry.RegistrationID, m_RegistrationLifetime);
                }
            }

            return null;
        }

        /// <summary>
        /// Used to resolve random entries in a specified mesh.
        /// </summary>
        /// <remarks>
        /// This method should allways return random entries if max is less than amount of entries.
        /// </remarks>
        /// <param name="resolveInfo"></param>
        /// <returns></returns>
        ResolveResponseInfo IPeerResolverContract.Resolve(ResolveInfo resolveInfo)
        {
            lock (m_SyncObj)
            {
                Cleanup();

                Mesh mesh = m_Meshes.Get(resolveInfo.MeshId);
                if (mesh != null)
                    return new ResolveResponseInfo(mesh.GetRegistredPeerNodes(resolveInfo.MaxAddresses));
            }

            return new ResolveResponseInfo();
        }

        /// <summary>
        /// Unregister an entry.
        /// </summary>
        /// <param name="unregisterInfo">Entry to unregister</param>
        void IPeerResolverContract.Unregister(UnregisterInfo unregisterInfo)
        {
            lock (m_SyncObj)
            {
                Cleanup();

                Mesh mesh = m_Meshes.Get(unregisterInfo.MeshId);
                if (mesh != null)
                    mesh.Remove(unregisterInfo.RegistrationId);
            }
        }

        /// <summary>
        /// Refresh the updated time of a entry.
        /// </summary>
        /// <param name="refreshInfo">Entry to update.</param>
        /// <returns></returns>
        RefreshResponseInfo IPeerResolverContract.Refresh(RefreshInfo refreshInfo)
        {
            lock (m_SyncObj)
            {
                Cleanup();

                Mesh mesh = m_Meshes.Get(refreshInfo.MeshId);
                if (mesh != null)
                {
                    if (mesh.Refresh(refreshInfo.RegistrationId, DateTime.UtcNow))
                        return new RefreshResponseInfo(m_RegistrationLifetime, RefreshResult.Success);
                }
            }

            return new RefreshResponseInfo(m_RegistrationLifetime, RefreshResult.RegistrationNotFound);
        }

        ServiceSettingsResponseInfo IPeerResolverContract.GetServiceSettings()
        {
            return new ServiceSettingsResponseInfo(true);
        }

        /// <summary>
        /// Get a cloned array of all meshes and entries.
        /// </summary>
        /// <returns></returns>
        public Mesh[] GetMeshes()
        {
            lock(m_SyncObj)
            {
                Cleanup();
                return (from mesh in m_Meshes
                       select mesh.Clone()).ToArray();

            }
        }
    }
}

